//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

/* 

	This is an example to demonstrate the use of a rotation manipulator through
	a rotation tool and context.  This example uses three classes to accomplish
	this task: First, a context command (rotateContext) is provided to create
	instances of the context.  Next, a custom selection context
	(RotateManipContext) is created to manage the rotation manipulator.  
	Finally, the rotation manipulator is provided as a custom node class.
	
	Loading and unloading:
	----------------------

	The rotate manipulator context can be created with the 
	following mel commands:
    
		rotateContext;
		setToolTo rotateContext1;

	If the preceding commands were used to create the manipulator context, 
	the following commands can destroy it:

		deleteUI rotateContext1;
		deleteUI rotateManip;

	If the plugin is loaded and unloaded frequently (eg. during testing),
	it is useful to make these command sequences into shelf buttons.

	How to use:
	-----------

	Once the tool button has been created using the script above, select the
	tool button then click on an object.  The rotate manipulator should appear
	at the center of the selected object.  The rotate manipulator can be used
	much like the built-in rotate manipulator.  In addition, the plugin 
	produces a state manipulator that can be used to control the modes for the
	rotation manipulator.  The state manipulator should be displayed 2 units 
	along the X-axis from the object.
	
*/

#include <maya/MIOStream.h>
#include <stdio.h>
#include <stdlib.h>

#include <maya/MFn.h>
#include <maya/MPxNode.h>
#include <maya/MPxManipContainer.h>
#include <maya/MPxSelectionContext.h>
#include <maya/MPxContextCommand.h>
#include <maya/MModelMessage.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MItSelectionList.h>
#include <maya/MPoint.h>
#include <maya/MVector.h>
#include <maya/MDagPath.h>
#include <maya/MManipData.h>
#include <maya/MEulerRotation.h>

// Manipulators
#include <maya/MFnRotateManip.h>
#include <maya/MFnStateManip.h>


// This function is a utility that can be used to extract vector values from
// plugs.
//
MVector vectorPlugValue(const MPlug& plug) {
	if (plug.numChildren() == 3)
	{
		double x,y,z;
		MPlug rx = plug.child(0);
		MPlug ry = plug.child(1);
		MPlug rz = plug.child(2);
		rx.getValue(x);
		ry.getValue(y);
		rz.getValue(z);
		MVector result(x,y,z);
		return result;
	}
	else {
		MGlobal::displayError("Expected 3 children for plug "+MString(plug.name()));
		MVector result(0,0,0);
		return result;
	}
}

/////////////////////////////////////////////////////////////
//
// exampleRotateManip
//
// This class implements the example rotate manipulator.
//
/////////////////////////////////////////////////////////////

class exampleRotateManip : public MPxManipContainer
{
public:
	exampleRotateManip();
	virtual ~exampleRotateManip();
	
	static void * creator();
	static MStatus initialize();
	virtual MStatus createChildren();
	virtual MStatus connectToDependNode(const MObject &node);

	virtual void draw(M3dView &view, 
					  const MDagPath &path, 
					  M3dView::DisplayStyle style,
					  M3dView::DisplayStatus status);

	// Callback function
	MManipData rotationChangedCallback(unsigned index);

public:
	static MTypeId id;

private:
	MDagPath fRotateManip;
	MDagPath fStateManip;

	unsigned rotatePlugIndex;
};


MTypeId exampleRotateManip::id( 0x80022 );

exampleRotateManip::exampleRotateManip() 
{ 
	// The constructor must not call createChildren for user-defined
	// manipulators.
}

exampleRotateManip::~exampleRotateManip() 
{
}


void *exampleRotateManip::creator()
{
	 return new exampleRotateManip();
}


MStatus exampleRotateManip::initialize()
{
	return MPxManipContainer::initialize();
}


MStatus exampleRotateManip::createChildren()
{
	MStatus stat = MStatus::kSuccess;

	// Add the rotation manip
	//
	fRotateManip = addRotateManip("RotateManip", "rotation");

	// Add the state manip.  The state manip is used to cycle through the 
	// rotate manipulator modes to demonstrate how they work.
	//
	fStateManip = addStateManip("StateManip", "state");

	// The state manip permits 4 states.  These correspond to:
	// 0 - Rotate manip in objectSpace mode
	// 1 - Rotate manip in worldSpace mode
	// 2 - Rotate manip in gimbal mode
	// 3 - Rotate manip in objectSpace mode with snapping on
	//
	// Note that while the objectSpace and gimbal modes will operator similar 
	// to the built-in Maya rotate manipulator, the worldSpace mode will 
	// produce unusual rotations because the plugin does not convert worldSpace
	// rotations to object space.
	//
	MFnStateManip stateManip(fStateManip);
	stateManip.setMaxStates(4);
	stateManip.setInitialState(0);
	
	return stat;
}


MStatus exampleRotateManip::connectToDependNode(const MObject &node)
{
	MStatus stat;

	// Find the rotate and rotatePivot plugs on the node.  These plugs will 
	// be attached either directly or indirectly to the manip values on the
	// rotate manip.
	//
	MFnDependencyNode nodeFn(node);
	MPlug rPlug = nodeFn.findPlug("rotate", &stat);
	if (!stat)
	{
		MGlobal::displayError("Could not find rotate plug on node");
		return stat;
	}
	MPlug rcPlug = nodeFn.findPlug("rotatePivot", &stat);
	if (!stat)
	{
		MGlobal::displayError("Could not find rotatePivot plug on node");
		return stat;
	}

	// If the translate pivot exists, it will be used to move the state manip
	// to a convenient location.
	//
	MPlug tPlug = nodeFn.findPlug("translate", &stat);

	// To avoid having the object jump back to the default rotation when the
	// manipulator is first used, extract the existing rotation from the node
	// and set it as the initial rotation on the manipulator.
	//
	MEulerRotation existingRotation(vectorPlugValue(rPlug));
	MVector existingTranslation(vectorPlugValue(tPlug));

	// 
	// The following code configures default settings for the rotate 
	// manipulator.
	//

	MFnRotateManip rotateManip(fRotateManip);
	rotateManip.setInitialRotation(existingRotation);
	rotateManip.setRotateMode(MFnRotateManip::kObjectSpace);
	rotateManip.displayWithNode(node);

	// Add a callback function to be called when the rotation value changes
	//
	rotatePlugIndex = addManipToPlugConversionCallback( rPlug, 
		(manipToPlugConversionCallback)
		&exampleRotateManip::rotationChangedCallback );

	// Create a direct (1-1) connection to the rotation center plug
	//
	rotateManip.connectToRotationCenterPlug(rcPlug);

	// Place the state manip at a distance of 2.0 units away from the object
	// along the X-axis.
	//
	MFnStateManip stateManip(fStateManip);
	stateManip.setTranslation(existingTranslation+MVector(2,0,0),
		MSpace::kTransform);

	finishAddingManips();
	MPxManipContainer::connectToDependNode(node);
	return stat;
}


void exampleRotateManip::draw(M3dView & view, 
					 const MDagPath & path, 
					 M3dView::DisplayStyle style,
					 M3dView::DisplayStatus status)
{
	// Uses default manipulator drawing to draw the rotate and state manips
	//
	MPxManipContainer::draw(view, path, style, status);
}

MManipData exampleRotateManip::rotationChangedCallback(unsigned index) {
	static MEulerRotation cache;
	MObject obj = MObject::kNullObj;

	// If we entered the callback with an invalid index, print an error and
	// return.  Since we registered the callback only for one plug, all 
	// invocations of the callback should be for that plug.
	//
	if (index != rotatePlugIndex)
	{
		MGlobal::displayError("Invalid index in rotation changed callback!");

		// For invalid indices, return vector of 0's
		MFnNumericData numericData;
		obj = numericData.create( MFnNumericData::k3Double );
		numericData.setData(0.0,0.0,0.0);

		return obj;
	}

	// Assign function sets to the manipulators
	//
	MFnStateManip stateManip(fStateManip);
	MFnRotateManip rotateManip(fRotateManip);

	// Adjust settings on the rotate manip based on the state of the state 
	// manip.
	//
	int mode = stateManip.state();
	if (mode != 3)
	{
		rotateManip.setRotateMode((MFnRotateManip::RotateMode) stateManip.state());
		rotateManip.setSnapMode(false);
	}
	else {
		// State 3 enables snapping for an object space manip.  In this case,
		// we snap every 15.0 degrees.
		//
		rotateManip.setRotateMode(MFnRotateManip::kObjectSpace);
		rotateManip.setSnapMode(true);
		rotateManip.setSnapIncrement(15.0);
	}

	// The following code creates a data object to be returned in the 
	// MManipData.  In this case, the plug to be computed must be a 3-component
	// vector, so create data as MFnNumericData::k3Double
	//
	MFnNumericData numericData;
	obj = numericData.create( MFnNumericData::k3Double );

	// Retrieve the value for the rotation from the manipulator and return it
	// directly without modification.  If the manipulator should eg. slow down
	// rotation, this method would need to do some math with the value before
	// returning it.
	//
	MEulerRotation manipRotation;
	if (!getConverterManipValue (rotateManip.rotationIndex(), manipRotation))
	{
		MGlobal::displayError("Error retrieving manip value");
		numericData.setData(0.0,0.0,0.0);
	}
	else {
		numericData.setData(manipRotation.x, manipRotation.y, manipRotation.z);
	}

	return MManipData(obj);
}

/////////////////////////////////////////////////////////////
//
// RotateManipContext
//
// This class is a simple context for supporting a rotate manipulator.
//
/////////////////////////////////////////////////////////////

class RotateManipContext : public MPxSelectionContext
{
public:
	RotateManipContext();
	virtual void	toolOnSetup(MEvent &event);
	virtual void	toolOffCleanup();

	// Callback issued when selection list changes
	static void updateManipulators(void * data);

private:
	MCallbackId id1;
};

RotateManipContext::RotateManipContext()
{
	MString str("Plugin Rotate Manipulator");
	setTitleString(str);
}


void RotateManipContext::toolOnSetup(MEvent &)
{
	MString str("Rotate the object using the rotation handles");
	setHelpString(str);

	updateManipulators(this);
	MStatus status;
	id1 = MModelMessage::addCallback(MModelMessage::kActiveListModified,
									 updateManipulators, 
									 this, &status);
	if (!status) {
		MGlobal::displayError("Model addCallback failed");
	}
}


void RotateManipContext::toolOffCleanup()
{
	MStatus status;
	status = MModelMessage::removeCallback(id1);
	if (!status) {
		MGlobal::displayError("Model remove callback failed");
	}
	MPxContext::toolOffCleanup();
}


void RotateManipContext::updateManipulators(void * data)
{
	MStatus stat = MStatus::kSuccess;
	
	RotateManipContext * ctxPtr = (RotateManipContext *) data;
	ctxPtr->deleteManipulators(); 

	// Add the rotate manipulator to each selected object.  This produces 
	// behavior different from the default rotate manipulator behavior.  Here,
	// a distinct rotate manipulator is attached to every object.
	// 
	MSelectionList list;
	stat = MGlobal::getActiveSelectionList(list);
	MItSelectionList iter(list, MFn::kInvalid, &stat);

	if (MS::kSuccess == stat) {
		for (; !iter.isDone(); iter.next()) {

			// Make sure the selection list item is a depend node and has the
			// required plugs before manipulating it.
			//
			MObject dependNode;
			iter.getDependNode(dependNode);
			if (dependNode.isNull() || !dependNode.hasFn(MFn::kDependencyNode))
			{
				MGlobal::displayWarning("Object in selection list is not "
					"a depend node.");
				continue;
			}

			MFnDependencyNode dependNodeFn(dependNode);
			/* MPlug rPlug = */ dependNodeFn.findPlug("rotate", &stat);
			if (!stat) {
				MGlobal::displayWarning("Object cannot be manipulated: " +
					dependNodeFn.name());
				continue;
			}

			// Add manipulator to the selected object
			//
			MString manipName ("exampleRotateManip");
			MObject manipObject;
			exampleRotateManip* manipulator =
				(exampleRotateManip *) exampleRotateManip::newManipulator(
					manipName, 
					manipObject);

			if (NULL != manipulator) {
				// Add the manipulator
				//
				ctxPtr->addManipulator(manipObject);

				// Connect the manipulator to the object in the selection list.
				//
				if (!manipulator->connectToDependNode(dependNode))
				{
					MGlobal::displayWarning("Error connecting manipulator to"
						" object: " + dependNodeFn.name());
				}
			} 
		}
	}
}


/////////////////////////////////////////////////////////////
//
// rotateContext
//
// This is the command that will be used to create instances
// of our context.
//
/////////////////////////////////////////////////////////////

class rotateContext : public MPxContextCommand
{
public:
	rotateContext() {};
	virtual MPxContext * makeObj();

public:
	static void* creator();
};

MPxContext *rotateContext::makeObj()
{
	return new RotateManipContext();
}

void *rotateContext::creator()
{
	return new rotateContext;
}


///////////////////////////////////////////////////////////////////////
//
// The following routines are used to register/unregister
// the context and manipulator
//
///////////////////////////////////////////////////////////////////////

MStatus initializePlugin(MObject obj)
{
	MStatus status;
	MFnPlugin plugin(obj, PLUGIN_COMPANY, "6.0", "Any");

	status = plugin.registerContextCommand("rotateContext",
										   &rotateContext::creator);
	if (!status) {
		MGlobal::displayError("Error registering rotateContext command");
		return status;
	}

	status = plugin.registerNode("exampleRotateManip", exampleRotateManip::id, 
								 &exampleRotateManip::creator, &exampleRotateManip::initialize,
								 MPxNode::kManipContainer);
	if (!status) {
		MGlobal::displayError("Error registering rotateManip node");
		return status;
	}

	return status;
}


MStatus uninitializePlugin(MObject obj)
{
	MStatus status;
	MFnPlugin plugin(obj);

	status = plugin.deregisterContextCommand("rotateContext");
	if (!status) {
		MGlobal::displayError("Error deregistering rotateContext command");
		return status;
	}

	status = plugin.deregisterNode(exampleRotateManip::id);
	if (!status) {
		MGlobal::displayError("Error deregistering RotateManip node");
		return status;
	}

	return status;
}
